package io.github.yfblock.frame.Core.Servlet;

import freemarker.template.TemplateException;
import io.github.yfblock.frame.BootApplication;
import io.github.yfblock.frame.Core.Constant.AttributeParams;
import io.github.yfblock.frame.Annotations.Controller;
import io.github.yfblock.frame.Annotations.RequestMapping;
import io.github.yfblock.frame.Core.Entity.Module;
import io.github.yfblock.frame.Core.Template.Template;
import io.github.yfblock.frame.utils.ModelOperator;
import io.github.yfblock.frame.utils.ModulePropertiesUtil;
import io.github.yfblock.yfHotLoad.ClassSource.DirClassSource;
import io.github.yfblock.yfHotLoad.ClassSource.JarClassSource;
import io.github.yfblock.yfHotLoad.HotLoader;
import io.github.yfblock.yfHotLoad.Utils.FileUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

public class MultiModuleServlet extends UrlPatternServlet {
    protected String jarLibPath;                                     // 模块Jar包路径
    protected Map<String, String> jarMounted;                        // 已经挂载的Jar包
    protected HotLoader nativeHotLoader;                             // 本包加载工具
    protected AnnotationConfigApplicationContext context;            // 上下文
    protected List<Module> mountedModules;                           // 已经挂载的模块

    /**
     * 创建所需要的文件夹
     * @param path 根目录
     */
    public void mkdirPaths(String path) {
        // 获取路径
        File parentDir = new File(System.getProperty("user.dir") + File.separator + "target");
        rootFilePath = parentDir.getAbsolutePath();
        File templateDir = new File(parentDir.getAbsoluteFile() + File.separator + "templates");
        File staticDir = new File(parentDir.getAbsoluteFile() + File.separator + "static");
        templateDir.mkdirs();
        staticDir.mkdirs();
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        String path = this.getClass().getProtectionDomain().getCodeSource().getLocation().getFile();
        this.mkdirPaths(path);
        super.init(config);

        this.mountedModules = new ArrayList<>();

        if(path.endsWith(".jar")) {
            // 如果以jar包方式运行
            nativeHotLoader = new HotLoader(new JarClassSource(path, "io.github.yfblock.frame"));
        } else {
            // 如果以调试方式运行
            nativeHotLoader = new HotLoader(new DirClassSource(BootApplication.class.getClassLoader(),
                    "io.github.yfblock.frame"));
        }

        // todo 测试加载器
        context = new AnnotationConfigApplicationContext();

        // 已经挂载的模块
        jarMounted = new HashMap<>();


        String rootPath =  rootFilePath;
        if (rootPath != null)
            jarLibPath = rootPath + "/extra/";           // 额外的jar包的路径
        this.loadNativeClasses();
        this.loadExternalModules();

        if(config!=null) {
            if(config.getInitParameter("debugPath")!=null) {
                String debugPath = config.getInitParameter("debugPath");
                String debugPackage = config.getInitParameter("debugPackage");
                String externalUrl = config.getInitParameter("debugUrl");
                this.loadDebugClasses(debugPath, debugPackage, externalUrl);
            }
        }

        // 刷新bean完成条件加载
        try {
            this.context.refresh();
        } catch (UnsatisfiedDependencyException e) {
            // bean不满足依赖 注入失败
            e.printStackTrace();
        }
    }

    @Override
    public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        super.service(req, res);
        req.setAttribute(AttributeParams.JAR_LIB_PATH, jarLibPath);
        req.setAttribute(AttributeParams.MODULE_MOUNTED_MAP, jarMounted);

        // 查找请求所属模块
        Module maxMatchModule = null;
        for(Module module : mountedModules) {
            if(requestUrl.indexOf(module.getExternal()) == 0) {
                if(maxMatchModule!=null) {
                    if(module.getExternal().length() < maxMatchModule.getExternal().length())
                        continue;
                }
                maxMatchModule = module;
            }
        }
        if(maxMatchModule!=null) {
            req.setAttribute(AttributeParams.CURRENT_MODULE, maxMatchModule.getName());
        }
    }

    /**
     * 加载调试控制类
     * @param path          文件目录路径
     * @param packageName   类存在的包名称
     * @param extraUrl      模块url
     */
    public void loadDebugClasses(String path, String packageName, String extraUrl) {
        System.out.println("--------------------加载调试控制器------------------------------");
        HotLoader hotLoader = new HotLoader(new DirClassSource(path, packageName));
        File staticDirFile = new File(path);
        this.handleHotLoader(hotLoader, extraUrl);
        copyFiles(staticDirFile.getAbsolutePath(), extraUrl.replaceAll("/", ""));
        System.out.println("------------------加载调试控制器加载完毕--------------------------");
    }

    /**
     * 加载本地的控制器类
     */
    public void loadNativeClasses() {
        System.out.println("--------------------加载本地控制器------------------------------");
        this.handleHotLoader(this.nativeHotLoader, "/");
        System.out.println("------------------加载本地控制器加载完毕--------------------------");
    }

    /**
     * 加载单个Jar包
     *
     * @param jarPath Jar包路劲，目前仅仅可以在特定的文件夹下 extra 文件夹下
     */
    public void loadSingleExternalClasses(String jarPath, String jarName, String extraUrl) {
        // 初始化extraUrl
        extraUrl = extraUrl.toLowerCase(Locale.ROOT);
        // 创建包读取节点
        HotLoader hotLoader = new HotLoader(new JarClassSource(jarPath + jarName));
        // Jar包分析器对Jar包进行分析
        this.handleHotLoader(hotLoader, extraUrl);

        // -----------预留功能 需要对jar包内的css、js、图片等静态资源进行解压或者代理----------
        // 实现代码


        // 加入到已挂载模块中
        jarMounted.put(jarName, extraUrl);
    }

    /**
     * 单个模块卸载
     * @param jarName 模块名称
     * @param extraUrl 模块url
     */
    public void unloadSingleExternClasses(String jarName, String extraUrl) {
        // 初始化extraUrl
        extraUrl = extraUrl.toLowerCase(Locale.ROOT);
        // 包卸载 遍历方式找到要卸载的模块 然后进行卸载
        this.removeNode(extraUrl);

        // todo -----------------预留功能 需要对jar包内的css、js、图片等静态资源进行删除------------
        // 实现代码

        // 加入到已挂载模块中
        jarMounted.remove(jarName);
    }

    /**
     * 加载外部的控制器类
     */
    public void loadExternalModules() {
        System.out.println("--------------------加载外部控制器------------------------------");
        // 遍历外部Jar包
        for (String jarFileName : FileUtil.getAllJarFiles(jarLibPath)) {
            // 读取已经重载的配置
            String propertiesFilePath = jarLibPath + jarFileName;
            ModulePropertiesUtil modulePropertiesUtil = new ModulePropertiesUtil(propertiesFilePath);
            if (modulePropertiesUtil.getDefaultLoad()) {
                this.loadSingleExternalClasses(jarLibPath, jarFileName, modulePropertiesUtil.getExtraUrl());
            }
            modulePropertiesUtil.store();
        }
        System.out.println("------------------加载外部控制器加载完毕--------------------------");
    }

    /**
     * 为函数组装参数， 根据参数类型获取参数
     * @param parameter 参数名称
     * @return 参数对象
     */
    @Override
    protected Object buildParameter(Parameter parameter) {
        try{
            Object obj =  this.context.getBean(parameter.getType());
            if(obj.getClass().equals(ModelOperator.class))
                ((ModelOperator)obj).initRequestAndResponse(request, response);
            return obj;
        } catch (BeansException ignored) {

        }

        return super.buildParameter(parameter);
    }

    /**
     * 处理加载包
     * @param hotLoader 热加载器
     * @param extraUrl 模块路径
     */
    protected void handleHotLoader(HotLoader hotLoader, String extraUrl) {
        // 储存已经加载的模块
        Module module = new Module(extraUrl.replaceAll("/", ""), extraUrl,
                null, true);
        mountedModules.add(module);

        // 加载Beans
        this.loadBeans(hotLoader);

        // 加载Controller
        for (Class<?> cls : hotLoader.getClassesByAnnotation(Controller.class))
            this.handleControllerClass(extraUrl, cls);
    }

    /**
     * 获取所有bean
     * @param hotLoader 热加载器
     */
    public void loadBeans(HotLoader hotLoader) {
        for(Class<?> cls : hotLoader.getClassesByAnnotation(Configuration.class)) {
            System.out.println("加载Bean:" + cls);
            this.context.register(cls);
        }
    }

    /**
     * 加载类中的方法列表到树
     *
     * @param prefixUrl             方法前缀url
     * @param cls                   控制器类
     */
    public void handleMethods(String prefixUrl, Class<?> cls) {
        for (Method method : cls.getDeclaredMethods()) {
            RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
            if (requestMapping == null) continue;
            this.addNode(prefixUrl + requestMapping.value(), method, cls);
        }
    }

    /**
     * 处理控制器类，实例化并且加载进入url列表
     * @param extraUrl 模块路径
     * @param cls 类名称
     */
    public void handleControllerClass(String extraUrl,  Class<?> cls) {
        RequestMapping requestMapping = cls.getAnnotation(RequestMapping.class);
        String url = requestMapping == null ? "/" : requestMapping.value();
        // 实例化 并且加入Bean
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(cls);
        this.context.registerBeanDefinition(cls.getName(), builder.getBeanDefinition());
        // 处理类中的方法
        this.handleMethods(extraUrl + url, cls);
        System.out.println("加载控制器:" + cls.getName());
    }

    /**
     * 复制资源文件
     * @param sourcePath 源目录
     * @param moduleName 模块名称
     */
    public void copyFiles(String sourcePath, String moduleName) {
        File destination = new File(rootFilePath);
        File templateDir = new File(sourcePath + File.separator + "templates");
        File staticDir = new File(sourcePath + File.separator + "static");
        if(templateDir.exists()) copyDir(templateDir.getAbsolutePath(),
                destination.getAbsolutePath() + File.separator + "templates" + File.separator + moduleName);
        if(staticDir.exists()) copyDir(staticDir.getAbsolutePath(),
                destination.getAbsolutePath() + File.separator + "static" + File.separator + moduleName);
    }

    /**
     * 复制文件夹
     * @param sourceDir 源文件夹
     * @param destDir 目标文件夹
     */
    public void copyDir(String sourceDir, String destDir) {
        try {
            Files.createDirectories(Paths.get(destDir));
            Files.walk(Paths.get(sourceDir)).forEach(source -> {
                Path destination = Paths.get(destDir, source.toString().substring(sourceDir.length()));
                try {
                    if(Files.exists(destination)) {
                        if(Files.isDirectory(source)) return;
                        else Files.delete(destination);
                    }
                    Files.copy(source, destination);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void renderTemplate(Template obj) throws IOException, TemplateException {
        // 重置obj路径
        String path = obj.getPath();
        if(path.indexOf('/') == 0) {
            // 如果是访问根目录的话（绝对路径）  如何处理
        } else {
            obj.setPath(request.getAttribute(AttributeParams.CURRENT_MODULE) + "/" + path);
        }
        super.renderTemplate(obj);
    }
}
